#!/usr/bin/env python3
"""
S3 Object Get Tool - GetObject API 演示工具

这个工具演示如何使用 GetObject API 从 S3 存储桶下载对象。

使用示例:
    python s3_object_get.py --bucket my-bucket --key test.txt
    python s3_object_get.py --bucket my-bucket --key test.txt --output local.txt
    python s3_object_get.py --bucket my-bucket --key test.txt --range "bytes=0-1023"
    python s3_object_get.py --bucket my-bucket --key test.txt --preview
    python s3_object_get.py --format json
    python s3_object_get.py --verbose
"""

import argparse
import boto3
import json
import sys
import os
from datetime import datetime, timezone
from botocore.config import Config
from botocore.exceptions import ClientError, NoCredentialsError


class ObjectGetClient:
    """S3 对象下载客户端"""
    
    def __init__(self, endpoint_url='http://localhost:8000', 
                 access_key='AKIAIOSFODNN7EXAMPLE', 
                 secret_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
                 region='us-east-1'):
        """
        初始化对象下载客户端
        
        Args:
            endpoint_url: S3 服务端点
            access_key: AWS Access Key
            secret_key: AWS Secret Key
            region: AWS 区域
        """
        self.endpoint_url = endpoint_url
        self.access_key = access_key
        self.secret_key = secret_key
        self.region = region
        self._client = None
    
    def _get_client(self):
        """获取 S3 客户端"""
        if self._client is None:
            self._client = boto3.client(
                's3',
                endpoint_url=self.endpoint_url,
                aws_access_key_id=self.access_key,
                aws_secret_access_key=self.secret_key,
                region_name=self.region,
                config=Config(s3={'addressing_style': 'path'})
            )
        return self._client
    
    def get_object(self, bucket_name, key, output_file=None, range_header=None, 
                         if_match=None, if_none_match=None, if_modified_since=None, 
                         if_unmodified_since=None, preview=False):
        """简单格式下载对象"""
        try:
            s3_client = self._get_client()
            
            # 准备参数
            get_args = {
                'Bucket': bucket_name,
                'Key': key
            }
            
            # Range 请求
            if range_header:
                get_args['Range'] = range_header
            
            # 条件请求
            if if_match:
                get_args['IfMatch'] = if_match
            if if_none_match:
                get_args['IfNoneMatch'] = if_none_match
            if if_modified_since:
                get_args['IfModifiedSince'] = if_modified_since
            if if_unmodified_since:
                get_args['IfUnmodifiedSince'] = if_unmodified_since
            
            print(f"📥 下载对象 s3://{bucket_name}/{key}")
            
            if range_header:
                print(f"   📊 Range 请求: {range_header}")
            
            if if_match:
                print(f"   🔍 If-Match: {if_match}")
            if if_none_match:
                print(f"   🔍 If-None-Match: {if_none_match}")
            if if_modified_since:
                print(f"   🔍 If-Modified-Since: {if_modified_since}")
            if if_unmodified_since:
                print(f"   🔍 If-Unmodified-Since: {if_unmodified_since}")
            
            response = s3_client.get_object(**get_args)
            
            # 显示响应头信息
            print(f"✅ 下载成功！")
            print(f"   🏷️  ETag: {response.get('ETag')}")
            print(f"   📄 Content-Type: {response.get('ContentType')}")
            print(f"   📊 Content-Length: {response.get('ContentLength')}")
            print(f"   📅 Last-Modified: {response.get('LastModified')}")
            
            if response.get('ContentRange'):
                print(f"   📊 Content-Range: {response.get('ContentRange')}")
            
            # 显示自定义元数据
            metadata = response.get('Metadata', {})
            if metadata:
                print(f"   🏷️  自定义元数据: {metadata}")
            
            # 读取并处理数据
            body = response['Body'].read()
            print(f"   📊 数据大小: {len(body)} 字节")
            
            if output_file:
                # 保存到文件
                output_dir = os.path.dirname(output_file)
                if output_dir:
                    os.makedirs(output_dir, exist_ok=True)
                with open(output_file, 'wb') as f:
                    f.write(body)
                print(f"   💾 已保存到: {output_file}")
            else:
                # 显示到控制台（仅文本内容）
                try:
                    content = body.decode('utf-8')
                    if preview:
                        print(f"\n📖 内容预览:")
                        print("-" * 40)
                        print(content[:500])
                        if len(content) > 500:
                            print("... (内容过长，已截断)")
                            print("提示：去掉 --preview 参数查看完整内容")
                        print("-" * 40)
                    else:
                        print(f"\n📖 完整内容:")
                        print("-" * 40)
                        print(content)
                        print("-" * 40)
                except UnicodeDecodeError:
                    print("\n⚠️  内容为二进制数据，无法显示")
            
            return True
            
        except ClientError as e:
            error_code = e.response.get('Error', {}).get('Code')
            error_message = e.response.get('Error', {}).get('Message')
            status_code = e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
            
            if status_code == 304:
                print("✅ 304 Not Modified - 对象未修改")
                return True
            elif status_code == 412:
                print("❌ 412 Precondition Failed - 前置条件失败")
            else:
                print(f"❌ 下载失败 [{error_code}]: {error_message}")
            return False
        except NoCredentialsError:
            print("❌ 错误: 未找到 AWS 凭据")
            return False
        except Exception as e:
            print(f"❌ 未知错误: {e}")
            return False
    
    def get_object_verbose(self, bucket_name, key, output_file=None, range_header=None, 
                          if_match=None, if_none_match=None, if_modified_since=None, 
                          if_unmodified_since=None, preview=False):
        """详细格式下载对象"""
        try:
            s3_client = self._get_client()
            
            print("📥 对象下载详细信息:")
            print("=" * 50)
            print(f"   🗂️  桶名称: {bucket_name}")
            print(f"   🔑 对象键名: {key}")
            print(f"   🌐 区域: {self.region}")
            print(f"   🔗 服务端点: {self.endpoint_url}")
            print(f"   📅 下载时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            
            if range_header:
                print(f"   📊 Range 请求: {range_header}")
            
            if if_match:
                print(f"   🔍 If-Match: {if_match}")
            if if_none_match:
                print(f"   🔍 If-None-Match: {if_none_match}")
            if if_modified_since:
                print(f"   🔍 If-Modified-Since: {if_modified_since}")
            if if_unmodified_since:
                print(f"   🔍 If-Unmodified-Since: {if_unmodified_since}")
            
            print()
            
            # 准备参数
            get_args = {
                'Bucket': bucket_name,
                'Key': key
            }
            
            if range_header:
                get_args['Range'] = range_header
            if if_match:
                get_args['IfMatch'] = if_match
            if if_none_match:
                get_args['IfNoneMatch'] = if_none_match
            if if_modified_since:
                get_args['IfModifiedSince'] = if_modified_since
            if if_unmodified_since:
                get_args['IfUnmodifiedSince'] = if_unmodified_since
            
            print(f"   🔧 正在下载对象...")
            response = s3_client.get_object(**get_args)
            
            print(f"   ✅ 下载成功！")
            print(f"   🏷️  ETag: {response.get('ETag')}")
            print(f"   📄 Content-Type: {response.get('ContentType')}")
            print(f"   📊 Content-Length: {response.get('ContentLength')}")
            print(f"   📅 Last-Modified: {response.get('LastModified')}")
            
            if response.get('ContentRange'):
                print(f"   📊 Content-Range: {response.get('ContentRange')}")
            
            # 显示自定义元数据
            metadata = response.get('Metadata', {})
            if metadata:
                print(f"   🏷️  自定义元数据: {metadata}")
            
            # 读取并处理数据
            body = response['Body'].read()
            print(f"   📊 数据大小: {len(body)} 字节")
            
            if output_file:
                # 保存到文件
                output_dir = os.path.dirname(output_file)
                if output_dir:
                    os.makedirs(output_dir, exist_ok=True)
                with open(output_file, 'wb') as f:
                    f.write(body)
                print(f"   💾 已保存到: {output_file}")
            else:
                # 显示到控制台（仅文本内容）
                try:
                    content = body.decode('utf-8')
                    if preview:
                        print(f"\n   📖 内容预览:")
                        print("   " + "-" * 40)
                        print("   " + content[:500])
                        if len(content) > 500:
                            print("   ... (内容过长，已截断)")
                            print("   提示：去掉 --preview 参数查看完整内容")
                        print("   " + "-" * 40)
                    else:
                        print(f"\n   📖 完整内容:")
                        print("   " + "-" * 40)
                        print("   " + content)
                        print("   " + "-" * 40)
                except UnicodeDecodeError:
                    print("\n   ⚠️  内容为二进制数据，无法显示")
            
            print(f"   📊 下载完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            
            return True
            
        except ClientError as e:
            error_code = e.response.get('Error', {}).get('Code')
            error_message = e.response.get('Error', {}).get('Message')
            status_code = e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
            
            print(f"   ❌ 下载失败:")
            print(f"      错误代码: {error_code}")
            print(f"      错误信息: {error_message}")
            
            if status_code == 304:
                print(f"   📋 状态: 304 Not Modified - 对象未修改")
                return True
            elif status_code == 412:
                print(f"   📋 状态: 412 Precondition Failed - 前置条件失败")
            else:
                print(f"   📋 状态: 下载失败")
            
            return False
        except NoCredentialsError:
            print("   ❌ 错误: 未找到 AWS 凭据")
            return False
        except Exception as e:
            print(f"   ❌ 未知错误: {e}")
            return False
    
    def get_object_json(self, bucket_name, key, output_file=None, range_header=None, 
                       if_match=None, if_none_match=None, if_modified_since=None, 
                       if_unmodified_since=None, preview=False):
        """JSON 格式下载对象"""
        try:
            s3_client = self._get_client()
            
            # 准备参数
            get_args = {
                'Bucket': bucket_name,
                'Key': key
            }
            
            if range_header:
                get_args['Range'] = range_header
            if if_match:
                get_args['IfMatch'] = if_match
            if if_none_match:
                get_args['IfNoneMatch'] = if_none_match
            if if_modified_since:
                get_args['IfModifiedSince'] = if_modified_since
            if if_unmodified_since:
                get_args['IfUnmodifiedSince'] = if_unmodified_since
            
            response = s3_client.get_object(**get_args)
            
            # 读取数据
            body = response['Body'].read()
            
            result = {
                "success": True,
                "bucket_name": bucket_name,
                "key": key,
                "etag": response.get('ETag'),
                "content_type": response.get('ContentType'),
                "content_length": response.get('ContentLength'),
                "last_modified": response.get('LastModified').isoformat() if response.get('LastModified') else None,
                "metadata": response.get('Metadata', {}),
                "size_bytes": len(body),
                "download_time": datetime.now().isoformat()
            }
            
            if range_header:
                result["range_request"] = range_header
            
            if response.get('ContentRange'):
                result["content_range"] = response.get('ContentRange')
            
            if output_file:
                # 保存到文件
                output_dir = os.path.dirname(output_file)
                if output_dir:
                    os.makedirs(output_dir, exist_ok=True)
                with open(output_file, 'wb') as f:
                    f.write(body)
                result["output_file"] = output_file
            else:
                # 尝试解码为文本
                try:
                    content = body.decode('utf-8')
                    if preview:
                        result["content_preview"] = content[:500]
                        result["content_truncated"] = len(content) > 500
                    else:
                        result["content"] = content
                except UnicodeDecodeError:
                    result["content_type"] = "binary"
                    result["content_preview"] = "Binary data, cannot display"
            
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return True
            
        except ClientError as e:
            error_code = e.response.get('Error', {}).get('Code')
            error_message = e.response.get('Error', {}).get('Message')
            status_code = e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
            
            if status_code == 304:
                result = {
                    "success": True,
                    "bucket_name": bucket_name,
                    "key": key,
                    "status": "304 Not Modified",
                    "message": "对象未修改",
                    "download_time": datetime.now().isoformat()
                }
            else:
                result = {
                    "success": False,
                    "bucket_name": bucket_name,
                    "key": key,
                    "error": error_code,
                    "error_message": error_message,
                    "status_code": status_code,
                    "download_time": datetime.now().isoformat()
                }
            
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return status_code == 304
        except NoCredentialsError:
            result = {
                "success": False,
                "error": "NoCredentials",
                "error_message": "未找到 AWS 凭据",
                "download_time": datetime.now().isoformat()
            }
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False
        except Exception as e:
            result = {
                "success": False,
                "error": "Unknown",
                "error_message": str(e),
                "download_time": datetime.now().isoformat()
            }
            print(json.dumps(result, indent=2, ensure_ascii=False))
            return False


def parse_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description='S3 GetObject API 演示工具',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  %(prog)s --bucket my-bucket --key test.txt                    # 下载到控制台
  %(prog)s --bucket my-bucket --key test.txt --output local.txt # 下载到文件
  %(prog)s --bucket my-bucket --key test.txt --range "bytes=0-1023"  # 范围下载
  %(prog)s --bucket my-bucket --key test.txt --preview          # 预览模式
  %(prog)s --format json                                        # JSON 格式输出
  %(prog)s --verbose                                            # 详细格式输出
  %(prog)s --access-key KEY --secret-key SECRET                 # 指定凭据
        """
    )
    
    parser.add_argument(
        '--host',
        default='http://localhost:8000',
        help='S3 服务端点 (默认: http://localhost:8000)'
    )
    
    parser.add_argument(
        '--access-key',
        default='AKIAIOSFODNN7EXAMPLE',
        help='AWS Access Key (默认: 测试密钥)'
    )
    
    parser.add_argument(
        '--secret-key',
        default='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
        help='AWS Secret Key (默认: 测试密钥)'
    )
    
    parser.add_argument(
        '--region',
        default='us-east-1',
        help='AWS 区域 (默认: us-east-1)'
    )
    
    parser.add_argument(
        '--format',
        choices=['simple', 'verbose', 'json'],
        default='simple',
        help='输出格式 (默认: simple)'
    )
    
    parser.add_argument(
        '--verbose',
        action='store_true',
        help='详细输出模式 (等同于 --format verbose)'
    )
    
    parser.add_argument(
        '--bucket',
        required=True,
        help='桶名称'
    )
    
    parser.add_argument(
        '--key',
        required=True,
        help='对象键名'
    )
    
    parser.add_argument(
        '--output',
        help='输出文件路径（默认显示到控制台）'
    )
    
    parser.add_argument(
        '--range',
        help='Range 请求，格式：bytes=start-end'
    )
    
    parser.add_argument(
        '--if-match',
        help='If-Match 条件（ETag）'
    )
    
    parser.add_argument(
        '--if-none-match',
        help='If-None-Match 条件（ETag）'
    )
    
    parser.add_argument(
        '--if-modified-since',
        help='If-Modified-Since 条件'
    )
    
    parser.add_argument(
        '--if-unmodified-since',
        help='If-Unmodified-Since 条件'
    )
    
    parser.add_argument(
        '--preview',
        action='store_true',
        help='预览模式（只显示前500字符）'
    )
    
    return parser.parse_args()


def main():
    """主函数"""
    args = parse_args()
    
    # 如果指定了 --verbose，覆盖 format 设置
    if args.verbose:
        args.format = 'verbose'
    
    # 创建客户端
    client = ObjectGetClient(
        endpoint_url=args.host,
        access_key=args.access_key,
        secret_key=args.secret_key,
        region=args.region
    )
    
    # 根据格式执行相应操作
    success = False
    if args.format == 'simple':
        success = client.get_object_simple(
            args.bucket, args.key, args.output, args.range,
            args.if_match, args.if_none_match, args.if_modified_since,
            args.if_unmodified_since, args.preview
        )
    elif args.format == 'verbose':
        success = client.get_object_verbose(
            args.bucket, args.key, args.output, args.range,
            args.if_match, args.if_none_match, args.if_modified_since,
            args.if_unmodified_since, args.preview
        )
    elif args.format == 'json':
        success = client.get_object_json(
            args.bucket, args.key, args.output, args.range,
            args.if_match, args.if_none_match, args.if_modified_since,
            args.if_unmodified_since, args.preview
        )
    
    # 设置退出码
    sys.exit(0 if success else 1)


if __name__ == '__main__':
    main()
